<?php
// +---------------------------------------------------------------------+
// | OneBase    | [ WE CAN DO IT JUST THINK ]                            |
// +---------------------------------------------------------------------+
// | Licensed   | http://www.apache.org/licenses/LICENSE-2.0 )           |
// +---------------------------------------------------------------------+
// | Author     | Bigotry <3162875@qq.com>                               |
// +---------------------------------------------------------------------+
// | Repository | https://gitee.com/Bigotry/OneBase                      |
// +---------------------------------------------------------------------+

/**
 * 应用公共（函数）文件
 */

use think\facade\Db;
use think\Response;
use think\exception\HttpResponseException;
use app\common\logic\File as LogicFile;


// +---------------------------------------------------------------------+
// | 系统相关函数
// +---------------------------------------------------------------------+

/**
 * 检测用户是否登录
 * @return integer 0-未登录，大于0-当前登录用户ID
 */
function is_login()
{
    
    $member = session('member_auth');
    
    if (empty($member)) {
        
        return DATA_DISABLE;
    } else {
        
        return session('member_auth_sign') == data_auth_sign($member) ? $member['member_id'] : DATA_DISABLE;
    }
}

/**
 * 系统非常规MD5加密方法
 * @param  string $str 要加密的字符串
 * @return string 
 */
function data_md5($str, $key = 'OneBase')
{
    
    return '' === $str ? '' : md5(sha1($str) . $key);
}

/**
 * 使用上面的函数与系统加密KEY完成字符串加密
 * @param  string $str 要加密的字符串
 * @return string 
 */
function data_md5_key($str, $key = '')
{
    
    if (is_array($str)) {
        
        ksort($str);

        $data = http_build_query($str);
        
    } else {
        
        $data = (string) $str;
    }
    
    return empty($key) ? data_md5($data, SYS_ENCRYPT_KEY) : data_md5($data, $key);
}

/**
 * 数据签名认证
 * @param  array  $data 被认证的数据
 * @return string       签名
 */
function data_auth_sign($data)
{
    
    // 数据类型检测
    if (!is_array($data)) {
        
        $data = (array)$data;
    }
    
    // 排序
    ksort($data);
    
    // url编码并生成query字符串
    $code = http_build_query($data);
    
    // 生成签名
    $sign = sha1($code);
    
    return $sign;
}

/**
 * 检测当前用户是否为管理员
 * @return boolean true-管理员，false-非管理员
 */
function is_administrator($member_id = null)
{
    
    $return_id = is_null($member_id) ? is_login() : $member_id;
    
    return $return_id && (intval($return_id) === SYS_ADMINISTRATOR_ID);
}

/**
 * 获取单例对象
 */
function get_sington_object($object_name = '', $class = null)
{
    app()->has($object_name) ?: app()->instance($object_name, new $class());
    
    return app($object_name);
}

/**
 * 获取插件类的类名
 * @param string $name 插件名
 */
function get_addon_class($name = '')
{
    
    $lower_name = strtolower($name);
    
    $class = SYS_ADDON_DIR_NAME. SYS_DS_CONS . $lower_name . SYS_DS_CONS . $name;
    
    return $class;
}

/**
 * 钩子
 */
function hook($tag = '', $params = [])
{
    
    \think\facade\Event::trigger($tag, $params);
}

/**
 * 插件显示内容里生成访问插件的url
 * @param string $url url
 * @param array $param 参数
 */
function addons_url($url, $param = array())
{

    $parse_url  =  parse_url($url);
    $addons     =  $parse_url['scheme'];
    $controller =  $parse_url['host'];
    $action     =  $parse_url['path'];

    /* 基础参数 */
    $params_array = array(
        'addon_name'      => $addons,
        'controller_name' => $controller,
        'action_name'     => substr($action, 1),
    );

    $params = array_merge($params_array, $param); //添加额外参数
    
    return url('addon/execute', $params);
}

/**
 * 插件对象注入
 */
function addon_ioc($this_class, $name, $layer)
{
    
    if (!str_prefix($name, $layer))
    {
        throw new \Exception('逻辑与模型层引用需前缀:' . $layer);
    }

    $class_arr = explode(SYS_DS_CONS, get_class($this_class));

    $sr_name = sr($name, $layer);

    $class_logic = SYS_ADDON_DIR_NAME . SYS_DS_CONS . $class_arr[DATA_NORMAL] . SYS_DS_CONS . $layer . SYS_DS_CONS . $sr_name;

    return get_sington_object(SYS_ADDON_DIR_NAME . '_' . $layer . '_' . $sr_name, $class_logic);
}

/**
 * 抛出响应异常
 */
function throw_response_exception($data = [], $type = 'json')
{
    
    $response = Response::create($data, $type);

    throw new HttpResponseException($response);
}

/**
 * 获取访问token
 * 根据OneBase规范：md5('OneBase' + date("Ymd") + API_KEY)
 */
function get_access_token()
{

    return md5('1yan9ding' . date("Ymd") . API_KEY);
}

/**
 * 格式化字节大小
 * @param  float $size      字节数
 * @param  string $delimiter 数字和单位分隔符
 * @return string            格式化后的带单位的大小
 */
function format_bytes($size, $delimiter = '')
{
    
    $units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
    
    for ($i = 0; $size >= 1024 && $i < 5; $i++) {
        
        $size /= 1024;
    }
    
    return round($size, 2) . $delimiter . $units[$i];
}


// +---------------------------------------------------------------------+
// | 数组相关函数
// +---------------------------------------------------------------------+

/**
 * 把返回的数据集转换成Tree
 * @param array $list 要转换的数据集
 * @param string $pid parent标记字段
 * @param string $level level标记字段
 * @return array
 */
function list_to_tree($list, $pk='id', $pid = 'pid', $child = '_child', $root = 0)
{
    
    // 创建Tree
    $tree = [];
    
    if (!is_array($list)) {
        
        return false;
    }
    
    // 创建基于主键的数组引用
    $refer = [];

    foreach ($list as $key => $data) {

        $refer[$data[$pk]] =& $list[$key];
    }

    foreach ($list as $key => $data) {

        // 判断是否存在parent
        $parentId =  $data[$pid];

        if ($root == $parentId) {

            $tree[] =& $list[$key];

        } else if (isset($refer[$parentId])){

            is_object($refer[$parentId]) && $refer[$parentId] = $refer[$parentId]->toArray();
            
            $parent =& $refer[$parentId];

            $parent[$child][] =& $list[$key];
        }
    }
    
    return $tree;
}

/**
 * 分析数组及枚举类型配置值 格式 a:名称1,b:名称2
 * @return array
 */
function parse_config_attr($string)
{
    
    // 兼容数组配置：如果传入是数组，直接按值返回
    if (is_array($string)) {
        // 保留原有顺序，去除键影响
        return array_values($string);
    }

    // 兼容空值与非字符串
    if ($string === null) {
        return [];
    }

    $string = (string) $string;
    $string = trim($string, ",;\r\n");
    if ($string === '') {
        return [];
    }

    $array = preg_split('/[,;\r\n]+/', $string);

    if (strpos($string, ':') !== false) {
        
        $value = [];
        
        foreach ($array as $val) {
            if ($val === '') continue;
            // 仅分割一次，避免值中包含冒号的情况
            [$k, $v] = array_pad(explode(':', $val, 2), 2, '');
            $value[$k] = $v;
        }
        
    } else {
        
        $value = $array;
    }
    
    return $value;
}

/**
 * 解析数组配置
 */
function parse_config_array($name = '')
{
    
    // 兼容升级后 config() 可能返回数组的情况
    $conf = config($name);

    // 如果是数组且包含 'value'，取其 'value' 作为待解析字符串
    if (is_array($conf)) {
        if (array_key_exists('value', $conf) && is_string($conf['value'])) {
            $conf = $conf['value'];
        } else {
            // 若已经是映射数组（如 ['1'=>'基础','2'=>'数据'...]），直接返回
            return $conf;
        }
    }

    return parse_config_attr($conf);
}

/**
 * 将二维数组数组按某个键提取出来组成新的索引数组
 */
function array_extract($array = [], $key = 'id')
{
    
    $count = count($array);
    
    $new_arr = [];
     
    for($i = 0; $i < $count; $i++) {
        
        if (!empty($array) && !empty($array[$i][$key])) {
            
            $new_arr[] = $array[$i][$key];
        }
    }
    
    return $new_arr;
}

/**
 * 根据某个字段获取关联数组
 */
function array_extract_map($array = [], $key = 'id'){
    
    
    $count = count($array);
    
    $new_arr = [];
     
    for($i = 0; $i < $count; $i++) {
        
        $new_arr[$array[$i][$key]] = $array[$i];
    }
    
    return $new_arr;
}

/**
 * 页面数组提交后格式转换 
 */
function transform_array($array)
{

    $new_array = array();
    $key_array = array();

    foreach ($array as $key=>$val) {

        $key_array[] = $key;
    }

    $key_count = count($key_array);

    foreach ($array[$key_array[0]] as $i => $val) {
        
        $temp_array = array();

        for( $j=0;$j<$key_count;$j++ ){

            $key = $key_array[$j];
            $temp_array[$key] = $array[$key][$i];
        }

        $new_array[] = $temp_array;
    }

    return $new_array;
}

/**
 * 页面数组转换后的数组转json
 */
function transform_array_to_json($array)
{
    
    return json_encode(transform_array($array));
}

/**
 * 关联数组转索引数组
 */
function relevance_arr_to_index_arr($array)
{
    
    $new_array = [];
    
    foreach ($array as $v)
    {
        
        $temp_array = [];
        
        foreach ($v as $vv)
        {
            $temp_array[] = $vv;
        }
        
        $new_array[] = $temp_array;
    }
    
    return $new_array;
}

/**
 * 数组转换为字符串，主要用于把分隔符调整到第二个参数
 * @param  array  $arr  要连接的数组
 * @param  string $glue 分割符
 * @return string
 */
function arr2str($arr, $glue = ',')
{
    
    return implode($glue, $arr);
}

/**
 * 数组转字符串二维
 * @param  array  $arr  要连接的数组
 * @param  string $glue 分割符
 * @return string
 */
function arr22str($arr)
{
    
    $t ='' ;
    $temp = [];
    foreach ($arr as $v) {
        $v = join(",",$v);
        $temp[] = $v;
    }
    foreach ($temp as $v) {
        $t.=$v.",";
    }
    $t = substr($t, 0, -1);
    return $t;
}


// +---------------------------------------------------------------------+
// | 字符串相关函数
// +---------------------------------------------------------------------+

/**
 * 字符串转换为数组，主要用于把分隔符调整到第二个参数
 * @param  string $str  要分割的字符串
 * @param  string $glue 分割符
 * @return array
 */
function str2arr($str, $glue = ',')
{
    
    return explode($glue, $str);
}

/**
 * 字符串替换
 */
function sr($str = '', $target = '', $content = '')
{
    
    return str_replace($target, $content, $str);
}

/**
 * 字符串前缀验证
 */
function str_prefix($str, $prefix)
{
    
    return strpos($str, $prefix) === DATA_DISABLE ? true : false;
}

// +---------------------------------------------------------------------+
// | 文件相关函数
// +---------------------------------------------------------------------+

/**
 * 获取目录下所有文件
 */
function file_list($path = '')
{
    
    $file = scandir($path);
    
    foreach ($file as $k => $v) {
        
        if (is_dir($path . SYS_DS_PROS . $v)) {
            
            unset($file[$k]);
        }
    }
    
    return array_values($file);
}

/**
 * 获取目录列表
 */
function get_dir($dir_name)
{
    
    $dir_array = [];
    
    if (false != ($handle = opendir($dir_name))) {
        
        $i = 0;
        
        while (false !== ($file = readdir($handle))) {
            
            if ($file != "." && $file != ".."&&!strpos($file,".")) {
                
                $dir_array[$i] = $file;
                
                $i++;
            }
        }
        
        closedir($handle);
    }
    
    return $dir_array;
}

/**
 * 获取文件根目录
 */
function get_file_root_path()
{
    
    $root_arr = explode(SYS_DS_PROS, URL_ROOT);
    
    array_pop($root_arr);
    
    $root_url = arr2str($root_arr, SYS_DS_PROS);
    
    return $root_url . SYS_DS_PROS;
}

/**
 * 获取图片url
 */
function get_picture_url($id = 0)
{

    return (new LogicFile())->getPictureUrl($id);
}

/**
 * 获取头像图片url
 */
function get_head_picture_url($id = 0)
{

    return (new LogicFile())->getPictureUrl($id, true);
}

/**
 * 获取文件url
 */
function get_file_url($id = 0)
{
    
    return (new LogicFile())->getFileUrl($id);
}

/**
 * 删除所有空目录 
 * @param String $path 目录路径 
 */
function rm_empty_dir($path)
{
    
    if (!(is_dir($path) && ($handle = opendir($path))!==false)) {
        
        return false;
    }
      
    while(($file = readdir($handle))!==false)
    {

        if (!($file != '.' && $file != '..')) {
            
           continue;
        }
        
        $curfile = $path . SYS_DS_PROS . $file;// 当前目录

        if (!is_dir($curfile)) {
            
           continue;  
        }

        rm_empty_dir($curfile);

        if (count(scandir($curfile)) == 2) {
            
            rmdir($curfile);
        }
    }

    closedir($handle); 
}


// +---------------------------------------------------------------------+
// | 时间相关函数
// +---------------------------------------------------------------------+

/**
 * 时间戳格式化
 * @param int $time
 * @return string 完整的时间显示
 */
function format_time($time = null, $format='Y-m-d H:i:s')
{
    
    if (null === $time) {
        
        $time = TIME_NOW;
    }
    
    return date($format, intval($time));
}

/**
 * 获取指定日期段内每一天的日期
 * @param string $startdate 开始日期
 * @param string $enddate  结束日期
 * @return Array
 */
function get_date_from_range($startdate, $enddate)
{
    
  $stimestamp = strtotime($startdate);
  $etimestamp = strtotime($enddate);
  
  // 计算日期段内有多少天
  $days = ($etimestamp-$stimestamp)/86400+1;
  
  // 保存每天日期
  $date = [];
  
  for($i=0; $i<$days; $i++) {
      
      $date[] = date('Y-m-d', $stimestamp+(86400*$i));
  }
  
  return $date;
}

// +---------------------------------------------------------------------+
// | 调试函数
// +---------------------------------------------------------------------+

/**
 * 将数据保存为PHP文件，用于调试
 */
function sf($arr = [], $fpath = './test.php')
{
    
    $data = "<?php\nreturn ".var_export($arr, true).";\n?>";
    
    file_put_contents($fpath, $data);
}

/**
 * dump函数缩写
 */
function d($arr = [])
{
    dump($arr);
}

/**
 * dump与die组合函数缩写
 */
function dd($arr = [])
{
    dump($arr);die;
}


// +---------------------------------------------------------------------+
// | 其他函数
// +---------------------------------------------------------------------+

/**
 * 通过类创建逻辑闭包
 */
function create_closure($object = null, $method_name = '', $parameter = [])
{
    
    $func = function() use($object, $method_name, $parameter) {
        
                return call_user_func_array([$object, $method_name], $parameter);
            };
            
    return $func;
}

/**
 * 通过闭包控制缓存
 */
function auto_cache($key = '', $func = null, $time = 3)
{
    
    // 优先读取已有缓存
    $cached = cache($key);
    if (!empty($cached)) {
        return $cached;
    }

    // 闭包不可调用则直接记录并返回
    if (!is_callable($func)) {
        \think\facade\Log::error("auto_cache: func not callable for key={$key}");
        return $cached;
    }

    // 执行闭包（不吞异常，交给调用方）
    $result = null;
    try {
        $result = $func();
    } catch (\Throwable $e) {
        \think\facade\Log::error("auto_cache: closure throw for key={$key}: " . $e->getMessage() . "\n" . $e->getTraceAsString());
        throw $e;
    }

    // 记录结果类型，方便判定是否为 ORM 集合或模型对象
    $type = is_object($result) ? get_class($result) : gettype($result);
    \think\facade\Log::info("auto_cache: result type for key={$key}: {$type}");

    // 统一转换为“可序列化的纯数组”，避免文件缓存序列化 ORM 对象抛错
    $resultForCache = $result;
    try {
        if ($result instanceof \think\model\Collection || $result instanceof \think\Collection) {
            // 集合统一 toArray
            $resultForCache = $result->toArray();
        } elseif ($result instanceof \think\Model) {
            // 单模型统一 toArray
            $resultForCache = $result->toArray();
        } elseif (is_array($result)) {
            // 数组元素中若包含 Model/具有 toArray 的对象，逐项转换
            $resultForCache = array_map(function ($item) {
                if ($item instanceof \think\Model) {
                    return $item->toArray();
                }
                if (is_object($item) && method_exists($item, 'toArray')) {
                    return $item->toArray();
                }
                return $item;
            }, $result);
        }
    } catch (\Throwable $e) {
        \think\facade\Log::error("auto_cache: result toArray convert failed for key={$key}: " . $e->getMessage());
        // 转换失败时不缓存，直接返回原始结果（但不抛异常以不影响上层逻辑）
        return $result;
    }

    // 写缓存（记录写入异常）
    try {
        if (!empty($resultForCache)) {
            cache($key, $resultForCache, $time);
        }
    } catch (\Throwable $e) {
        \think\facade\Log::error("auto_cache: cache set failed for key={$key}: " . $e->getMessage() . "\n" . $e->getTraceAsString());
        // 写入失败仍返回数据，避免影响页面
        return $resultForCache;
    }

    return $resultForCache;
}

/**
 * 通过闭包列表控制事务
 */
function closure_list_exe($list = [])
{
    
    \think\facade\Db::startTrans();
    
    try {
        
        foreach ($list as $closure) {
            
            $closure();
        }
        
        \think\facade\Db::commit();
        
        return true;
    } catch (\Exception $e) {
        
        \think\facade\Db::rollback();
        
        throw $e;
    }
}

/**
 * 自动封装事务
 */
function trans($parameter = [], $callback = null)
{
    
    try {

        \think\facade\Db::startTrans();

        $backtrace = debug_backtrace(false, 2);

        array_shift($backtrace);

        $class = $backtrace[0]['class'];

        $result = (new $class())->$callback($parameter);

        \think\facade\Db::commit();

        return $result;

    } catch (Exception $ex) {

        \think\facade\Db::rollback();

        throw new Exception($ex->getMessage());
    }
}

/**
 * 更新缓存版本
 */
function update_cache_version($obj = null)
{
    
    $ob_auto_cache = cache('ob_auto_cache');

    is_string($obj) ? $ob_auto_cache[$obj]['version']++ : $ob_auto_cache[$obj->getTable()]['version']++;

    cache('ob_auto_cache', $ob_auto_cache);
}

/**
 * 获取通知订单号
 */
function get_order_sn()
{
   
    $where['service_name']  = 'Pay';
    $where['status']        = DATA_NORMAL;

    $pay_types = \think\facade\Db::name('driver')->where(normalize_where($where))->select();
    
    $order_sn = null;
    
    foreach ($pay_types as $v)
    {
        
        $model = model($v['driver_name'], 'service\\pay\\driver');
        
        $order_sn = $model->getOrderSn();
        
        if (!empty($order_sn)) {  break; }
    }
    
    return $order_sn;
}

/**
 * 获取统一的会话ID（优先TP框架，会话ID为空时回退原生）
 */
function sid()
{
    $tpSid = \think\facade\Session::getId();
    if (!empty($tpSid)) {
        return $tpSid;
    }
    return function_exists('session_id') ? session_id() : '';
}

/**
 * 视图小物件助手：调用小物件控制器方法并返回渲染内容
 * 用法示例：{:widget('editor/index', ['name'=> 'describe_text','value'=> ''])}
 * Author: 王勇程 <laughskydragon@qq.com>
 */
function widget($url = '', $params = [])
{
    try {
        if (empty($url)) {
            return '';
        }

        $url = trim($url, '/');
        $parts = explode('/', $url);
        $controller = $parts[0] ?? '';
        $action = $parts[1] ?? 'index';
        if ($controller === '') {
            return '';
        }

        // 依次尝试 admin 与 index 模块下的小物件类
        $classCandidates = [
            'app\\admin\\widget\\' . ucfirst($controller),
            'app\\index\\widget\\' . ucfirst($controller),
        ];

        $instance = null;
        foreach ($classCandidates as $class) {
            if (class_exists($class)) {
                // 使用容器创建实例，确保依赖正常注入
                $instance = app()->make($class);
                break;
            }
        }

        if (!$instance) {
            return '';
        }

        if (!method_exists($instance, $action)) {
            // 回退到默认 index 方法
            $action = 'index';
            if (!method_exists($instance, $action)) {
                return '';
            }
        }

        // 按方法形参名构造调用参数，增强健壮性
        $ref = new \ReflectionMethod($instance, $action);
        $args = [];

        // 若传入的是顺序数组，也能兼容处理
        $isAssoc = is_array($params) && array_keys($params) !== range(0, count($params) - 1);

        if ($isAssoc) {
            foreach ($ref->getParameters() as $p) {
                $name = $p->getName();
                if (array_key_exists($name, $params)) {
                    $args[] = $params[$name];
                } elseif ($p->isDefaultValueAvailable()) {
                    $args[] = $p->getDefaultValue();
                } else {
                    $args[] = null;
                }
            }
        } else {
            $args = array_values($params);
        }

        return $ref->invokeArgs($instance, $args);
    } catch (\Throwable $e) {
        // 静默失败，避免影响页面渲染
        return '';
    }
}